namespace Wilco.CodeHilighter.Engine.SyntaxHighlighting
{
    using System;
    using System.Drawing;
    using System.Linq;
    using System.Xml.Linq;

    /// <summary>
    /// Represents an ASPX scanner.
    /// </summary>
    public class ASPXScanner : ScannerBase
    {
        #region Fields

        private ASPXNode aspxNode;
        private string language;
        private IParser parser;
        private XmlScanner xmlScanner;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.ASPXScanner"/> class.
        /// </summary>
        public ASPXScanner()
            : this(null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.ASPXScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        public ASPXScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
            : this(tokenizer, scannerResult, new ASPXNode())
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.ASPXScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="aspxNode">A <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.ASPXNode"/> object.</param>
        public ASPXScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, ASPXNode aspxNode)
            : base(tokenizer, scannerResult)
        {
            this.aspxNode = aspxNode;
            this.aspxNode.BackColor = Color.Yellow;
            this.xmlScanner = new XmlScanner(this.Tokenizer, this.ScannerResult);
            this.xmlScanner.Match += new MatchEventHandler(this.xmlScanner_Match);
            this.parser = new HtmlParser();
            this.SetID("ASPXScanner");
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the script node.
        /// </summary>
        public ASPXNode ASPXNode
        {
            get
            {
                return this.aspxNode;
            }
            set
            {
                if (value != this.aspxNode)
                {
                    this.aspxNode = value;
                }
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new ASPXScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XElement element = (XElement)state;

            // Load settings.
            this.aspxNode.BackColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting","name", "BackColor"));
            this.aspxNode.ForeColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "ForeColor"));
            this.aspxNode.Font = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "Font");
            this.aspxNode.NavigateUrl = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "NavigateUrl");
        }

        /// <summary>
        /// Resets the scanner.
        /// </summary>
        public override void Reset()
        {
            this.language = null;
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            //XDocument document = (XDocument)container;
            XElement element = (XElement)base.SaveState(container);

            // Save settings.
            XElement settingRootElement = new XElement("settings");
            element.Add(settingRootElement);

            settingRootElement.Add(this.CreateSetting("BackColor", ColorTranslator.ToHtml(this.aspxNode.BackColor)));
            settingRootElement.Add(this.CreateSetting("ForeColor", ColorTranslator.ToHtml(this.aspxNode.ForeColor)));
            settingRootElement.Add(this.CreateSetting("ForeColor", this.aspxNode.Font));
            settingRootElement.Add(this.CreateSetting("NavigateUrl", this.aspxNode.NavigateUrl));

            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation will generally have a reference to a 
        /// <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!this.Enabled)
            {
                if (this.Child != null)
                {
                    this.Child.Scan(token);
                }
            }
            else
            {
                // Highlight as XML until a script block was found.
                if (this.language == null)
                {
                    bool handled = false;
                    if ((this.Tokenizer.Position + 3 <= this.Tokenizer.Source.Length) && this.Tokenizer.GetNextTokens(3) == "<%@")
                    {
                        int charIndex = this.Tokenizer.Source.IndexOf("%>", this.Tokenizer.Position + 3);
                        if (charIndex == -1)
                            charIndex = this.Tokenizer.Source.Length;
                        else
                            charIndex += 2;

                        this.ScannerResult.Add(new Occurrence(this.Tokenizer.Position, charIndex - this.Tokenizer.Position, this.aspxNode));

                        this.Tokenizer.MoveTo(charIndex);
                        handled = true;
                    }
                    else if (this.Tokenizer.Position + 2 <= this.Tokenizer.Source.Length)
                    {
                        string right = this.Tokenizer.GetNextTokens(2);
                        if (right == "<%" || right == "%>")
                        {
                            this.ScannerResult.Add(new Occurrence(this.Tokenizer.Position, 2, this.aspxNode));
                            handled = true;
                        }
                    }

                    if (!handled)
                    {
                        // Find script block.
                        this.xmlScanner.Scan(token);
                    }
                }
                else
                {
                    if ((this.Tokenizer.Position + 9) <= this.Tokenizer.Source.Length && this.Tokenizer.GetNextTokens(9) == "</script>")
                    {
                        this.language = null;
                        this.xmlScanner.Scan(token);
                    }
                    else if (this.Child != null)
                    {
                        this.Child.Scan(token);
                    }
                }
            }
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private XElement CreateSetting(string name, string value)
        {
            return new XElement("setting", new XAttribute("name", name));
        }

        /// <summary>
        /// Handles the match event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void xmlScanner_Match(object sender, MatchEventArgs e)
        {
            if (e.Tag != null && e.Tag.ToLower() == "script")
            {
                if (e.Type == MatchType.StartTag)
                {
                     if(e.Attributes.ContainsKey("lang"))
                        this.language=e.Attributes["lang"];
                    else if(e.Attributes.ContainsKey("language"))
                        this.language = e.Attributes["language"];

                    if (this.language != null)
                    {
                        this.language = this.language.Replace("\"", "").Replace("'", "");
                        if (this.language.Length == 0)
                        {
                            this.language = null;
                        }
                    }

                    if (this.language != null)
                    {
                        HighlighterBase highlighter = Register.Instance.Highlighters[this.language];
                        if (highlighter != null)
                        {
                            highlighter = highlighter.Create();
                            highlighter.Parser = this.parser;
                            this.Child = highlighter.BuildEntryPointScanner(this.Tokenizer, this.ScannerResult);
                        }
                        else
                            this.language = null;
                    }
                }
                else if (e.Type == MatchType.EndTag)
                    this.language = null;
            }
        }

        #endregion Methods
    }
}